from kivy.metrics import inch
from kivy.graphics.vertex_instructions import Ellipse, Rectangle

from dryfire.model.shapeModel import OutdoorShapeModel, DryFireShapeModel
from dryfire.model.spaceModel import OutdoorSpaceModel, DryFireSpaceModel

from math import floor

def moa_to_inches(moa):
    return(1.047 * moa)


class DryFireTargetPartNode(object):
    def __init__(self, target):
        self.rendered = False
        self.space_model = target
        self.x = 0
        self.y = 0
        self.node_up = None
        self.node_down = None
        self.node_left = None
        self.node_right = None
        self.version = 0


class DryFireTargetModel(object):
    def __init__(self, name):
        self._name = name
        self._parts_tree = None
        self._parts_list = []
        self._outdoor_distance_yards = 25
        self._indoor_distance_feet = 10
        self._description = ''
        # the view
        self._view_padding_left = 0
        self._view_padding_bottom = 0
        self._view_width = 0
        self._view_height = 0
        # graphics
        self._left = 0
        self._bottom = 0
        self._top = 0
        self._right = 0
        # sizing
        self._pixels_per_inch = inch(1)
        self._width_1ft = 0
        self._height_1ft = 0
        self._sizer_w = 0
        self._sizer_h = 0
        self._sizer_outdoor_distance_yards = 0

    # properties

    @property
    def name(self):
        return self._name

    @property
    def description(self):
        return self._description

    @property
    def size(self):
        w = self._right - self._left
        h = self._top - self._bottom
        return w, h

    @property
    def indoor_distance_feet(self):
        return self._indoor_distance_feet

    @indoor_distance_feet.setter
    def indoor_distance_feet(self, value):
        """
        type: value: float, int
        """
        if isinstance(value, float):
            value = floor(value)
        if self._indoor_distance_feet != value:
            self._indoor_distance_feet = value
            if self._parts_tree is not None:
                for i in self._parts_list:
                    i.space_model.distance_feet = value
                if self._view_width > 0 and self._view_height > 0:
                    self._position_nodes()

    @property
    def pixels_per_inch(self):
        return self._pixels_per_inch

    @pixels_per_inch.setter
    def pixels_per_inch(self, value):
        """
        type: value: float, int
        """
        if self._pixels_per_inch != value:
            self._pixels_per_inch = value
            if self._parts_tree is not None:
                for i in self._parts_list:
                    i.space_model.pixels_per_inch = value
                self._position_nodes()
                self._set_sizer()
        
    @property
    def sizer(self):
        """width and height in pixels to be added
        with each foot that the user is away from the screen.
        """
        adjuster = float(self._sizer_outdoor_distance_yards) / float(self._outdoor_distance_yards)
        w = adjuster * self._sizer_w
        h = adjuster * self._sizer_h
        return w, h
        
    @property
    def size_1f(self):
        """size in pixels for viewer at 1 foot from the computer screen.
        """
        adjuster = float(self._sizer_outdoor_distance_yards) / float(self._outdoor_distance_yards)
        w = adjuster * self._width_1ft
        h = adjuster * self._height_1ft
        return w, h

    # public methods
            
    def position_graphics(self, **kwargs):
        """
        This will position the node.
        They cannot be positioned without a view width and height.
        After calling this method
            target.size will return the size.
            target.
        """
        recalc = False
        if 'view_size' in kwargs:
            self._view_width = kwargs['view_size'][0]
            self._view_height = kwargs['view_size'][1]
        if 'view_width' in kwargs:
            self._view_width = kwargs['view_width']
        if 'view_height' in kwargs:
            self._view_height = kwargs['view_height']
        if 'pixels_per_inch' in kwargs:
            if self._pixels_per_inch != kwargs['pixels_per_inch']:
                recalc = True
                self._pixels_per_inch = kwargs['pixels_per_inch']
        if 'outdoor_distance_yards' in kwargs:
            d = kwargs['outdoor_distance_yards']
            if isinstance(d, float):
                d = floor(d)
            if d <= 25:
                d = 25
            if self._outdoor_distance_yards != d:
                recalc = True
                self._outdoor_distance_yards = d
        if 'indoor_distance_feet' in kwargs:
            d = kwargs['indoor_distance_feet']
            if isinstance(d, float):
                d = floor(d)
            if d <= 0:
                d = 10
            if self._indoor_distance_feet != d:
                recalc = True
                self._indoor_distance_feet = d
        if recalc is True:
            # recalc each part's width and height in pixels
            for i in self._parts_list:
                i.space_model.recalc(self._pixels_per_inch,
                                     self._indoor_distance_feet,
                                     self._outdoor_distance_yards)
        # calc padding left and bottom
        use_padding = kwargs.get('view_padding', True)
        if use_padding:
            w_szr, h_szr = self.sizer
            w_1ft, h_1ft = self.size_1f
            image_width = w_1ft + (self._indoor_distance_feet * w_szr)
            empty_space = self._view_width - image_width  
            self._view_padding_left = floor((empty_space) / 2)
            image_height = h_1ft + (self._indoor_distance_feet * h_szr) 
            empty_space = self._view_height - image_height  
            self._view_padding_bottom = floor((empty_space) / 2) 
        else:
            self._view_padding_left = 0
            self._view_padding_bottom = 0
        self._position_nodes()

    def get_graphics(self):
        """
        Converts the shapes into a list of kivy graphics with fixed positions.
        """
        graphics_list = []
        self._build_graphics_list(self._parts_tree,
                                  graphics_list)
        # return a copy of the list
        return graphics_list
        
    def calc_max_indoor_distance_for_view_size(self, view_size):
        # calc width
        w_1f, h_1f = self.size_1f
        w_szr, h_szr = self.sizer
        diff = view_size[0] - w_1f
        if w_szr > 0:
            w_ft = diff / w_szr
        else:
            w_ft = diff
        # calc height
        diff = view_size[1] - h_1f
        if h_szr > 0:
            h_ft = diff / h_szr
        else:
            h_ft = diff
        return floor(min(w_ft, h_ft))

    # private methods

    # private position methods

    def _position_nodes(self):
        if self._top == 0 and self._left == 0\
        and self._bottom == 0 and self._right == 0:
            node = self._parts_tree
            start_x = (self._view_width - node.space_model.width_pixels) / 2
            start_y = self._view_padding_bottom
            self._reposition_nodes(start_x, start_y)
        # the graphics positioned but not nessesarily centered.
        node = self._parts_tree
        start_x = (self._view_width - (self._right - self._left)) / 2
        start_y = (self._view_height - (self._top - self._bottom)) / 2
        self._reposition_nodes(start_x, start_y)
        
    def _reposition_nodes(self, start_x, start_y):
        node = self._parts_tree
        node.version ^= 1
        node.rendered = False
        node.x = start_x
        node.y = start_y
        self._bottom = node.y
        self._top = node.y + node.space_model.height_pixels
        self._left = node.x
        self._right = node.x + node.space_model.width_pixels
        if node.node_up is not None:
            self._recursive_position_nodes_up(node)
        if node.node_right is not None:
            self._recursive_position_nodes_right(node)
        if node.node_left is not None:
            self._recursive_position_nodes_left(node)

    def _recursive_position_nodes_up(self, node):
        # node.node_up is not None
        node_u = node.node_up
        if node_u.version != node.version:
            # this node has not been positioned
            node_u.version = node.version
            node_u.rendered = False
            # set the up node's y position
            node_u.y = node.y + node.space_model.height_pixels
            # align the centers along the x axes
            node_center_x = node.x + (node.space_model.width_pixels / 2)
            node_u.x = node_center_x - (node_u.space_model.width_pixels / 2)
            # all done with the up node
            # check top
            top = node_u.y + node_u.space_model.height_pixels
            if self._top < top:
                self._top = top
            # check right
            right = node_u.x + node_u.space_model.width_pixels
            if self._right < right:
                self._right = right
            # check left
            if self._left > node_u.x:
                self._left = node_u.x
            # continue with the other nodes around upnode
            if node_u.node_up is not None:
                self._recursive_position_nodes_up(node_u)
            if node_u.node_right is not None:
                self._recursive_position_nodes_right(node_u)
            if node_u.node_left is not None:
                self._recursive_position_nodes_left(node_u)

    def _recursive_position_nodes_right(self, node):
        # node.node_right is not None
        node_r = node.node_right
        if node_r.version != node.version:
            # this node has not been positioned
            node_r.version = node.version
            node_r.rendered = False
            node_r.x = node.x + node.space_model.width_pixels
            # align the middles along the y axes
            node_middle_y = node.y + (node.space_model.height_pixels / 2)
            node_r.y = node_middle_y - (node_r.space_model.height_pixels / 2)
            # all done with node right
            # check right
            right = node_r.x + node_r.space_model.width_pixels
            if self._right < right:
                self._right = right
            # check top
            top = node_r.y + node_r.space_model.height_pixels
            if self._top < top:
                self._top = top
            # check bottom
            if self._bottom > node_r.y:
                self._bottom = node_r.y
            # continue with the other nodes around node right
            if node_r.node_right is not None:
                self._recursive_position_nodes_right(node_r)
            if node_r.node_up is not None:
                self._recursive_position_nodes_up(node_r)

    def _recursive_position_nodes_left(self, node):
        node_l = node.node_left
        if node_l.version != node.version:
            # this node has not been positioned
            node_l.version = node.version
            node_l.rendered = False
            node_l.x = node.x - node_l.width_pixels
            # align the middles along the y axes
            node_middle_y = node.y + (node.space_model.height_pixels / 2)
            node_l.y = node_middle_y - (node_l.space_model.height_pixels / 2)
            # all done with node left
            # check left
            if self._left > node_l.x:
                self._left = node_l.x
            # check top
            top = node_l.y + node_l.space_model.height_pixels
            if self._top < top:
                self._top = top
            # check bottom
            if self._bottom > node_l.y:
                self._bottom = node_l.y
            # continue with nodes around node left
            if node_l.node_left is not None:
                self._recursive_position_nodes_left(node_l)
            if node_l.node_up is not None:
                self._recursive_position_nodes_up(node_l)

    def _build_graphics_list(self, node, graphics_list):
        # the order of the rendering is ambiguous.
        node.rendered = True
        target = node.space_model
        top = node.y + target.height_pixels
        if top < self._view_height:
            # not too high
            if node.x >= 0:
                # not too far left
                right = node.x + target.width_pixels
                if right <= self._view_width:
                    # not too far right
                    if isinstance(node.space_model, DryFireShapeModel):
                        # render the graphic
                        gg = node.space_model.get_graphic(node.x, node.y)
                        for g in gg:
                            graphics_list.append(g)
                    # continue with right
                    if node.node_right is not None and node.node_right.rendered is False:
                        self._build_graphics_list(node.node_right,
                                                  graphics_list)
                else:
                    # node too far right
                    pass
                # continue with left
                if node.node_left is not None and node.node_left.rendered is False:
                    self._build_graphics_list(node.node_left,
                                              graphics_list)
            else:
                # node too far left
                pass
            # continue with up
            if node.node_up is not None and node.node_up.rendered is False:
                self._build_graphics_list(node.node_up,
                                          graphics_list)
        else:
            # node too high
            pass

    # private sizer method
        
    def _set_sizer(self):
        """
        calc the width and height multipliers for indoor distances.
        example of using self.sizer:
        w, h = target.sizer
        max_w_feet = math.floor(view_w / w)
        max_h_feet = math.floor(view_h / h)
        max_feet = min(max_w_feet, max_h_feet)
        """
        self._sizer_outdoor_distance_yards = self._outdoor_distance_yards
        self.position_graphics(view_size=(5000, 5000),
                               view_padding=False,
                               indoor_distance_feet=1)
        self._width_1ft, self._height_1ft = self.size
        self.position_graphics(indoor_distance_feet=2)
        w2, h2 = self.size
        # self._sizer_w = # of pixels per foot distance
        self._sizer_w = w2 - self._width_1ft
        # self._sizer_h = # of pixels per foot distance
        self._sizer_h = h2 - self._height_1ft
